﻿/*
 * Title: YAOSXDockWannabe (Yet Another OSX Dock Wannabe)
 * Author: Tom Winans, http://www.concentrum.com
 * Date: Jan 2011
 * 
 * Inspiration: The wonderful minds behind Apple UI Nirvana ... in this case, those who created and maintain "the dock".
 * 
 * Disclaimer: Donated to the public domain with no warranties of any kind from the author.
 * 
 * Permission to use: Granted to any who might find this useful. Users are not authorized to sell this work (should it be saleable at all please let me know :-)). Please acknowledge authorship. 
 * Please drop me a note if you'd like - especially should this work turn out to be useful to you (tom dot winans at concentrum dot com). I'm new to this community and would enjoy making your
 * acquaintance.
 * 
 * Request: Surely there are plenty of improvements that can be made to this feeble effort ... please feel free to critique and advise of how this work could be improved. Make improvements! 
 * If you do make improvements, please update any accompanying documentation (i.e., at least code comments) to describe improvements made. And please let me know when you make improvements as 
 * I would like the benefit of same.
 * 
 * Technology stack: .NET 4.0, Silverlight 4, C#, VS 2010 and/or Expression Blend 4
 * 
 * Very helpful hints: 
 * Making a transparent button - http://stackoverflow.com/questions/672926/how-can-i-make-a-silverlight-2-0-button-entirely-transparent
 * 
 * NOTES: 
 * 13 Jan 2011/tom.winans
 * - Does not handle "getting close" to the dock. Later.
 * - No image reflection. That would be nice. Later.
 * - No tray (vs. bounding rectangle) when anchored to bottom. That would be nice. Later.
 */

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace YAOSXDockWannabe
{
    public enum DockOrientation { Left = 0, Bottom = 1, Right = 2 }

    public partial class MainPage : UserControl
    {
        #region Properties
        public double DefaultWidthHeight { get; set; }
        public double[] ScaleMultipliers { get; set; }

        private DockOrientation _orientation;
        public DockOrientation Orientation
        {
            get { return _orientation; }
            set
            {
                _orientation = value;
                this.ResetOrientation();
            }
        }

        private Rectangle DockBackground { get; set; }
        public Color DockBackgroundColor { get; set; }
        public Color DockBackgroundStroke { get; set; }
        public double DockBackgroundStrokeThickness { get; set; }
        #endregion

        #region Initialization
        private void LoadImages() {
            Color transparent = new Color();
            transparent.A = 0;
            transparent.R = transparent.G = transparent.B = 255;

            int index = 0;
            for (int i = 0; i < 10; i++)
            {
                Image image = new Image();

                index++;
                if (index > 7) index = 1;

                Uri uri = new Uri(String.Format("images/image{0}.png", index), UriKind.Relative);
                image.Source = new BitmapImage(uri);

                Button b = new Button();
                b.Background = new SolidColorBrush(transparent);
                b.Content = image;
                b.Template = new ControlTemplate();
                b.Template = (ControlTemplate)Application.Current.Resources["TransparentButton"];
                b.Click += new RoutedEventHandler(OnClick);
                b.MouseEnter += new MouseEventHandler(OnMouseEnter);
                b.SetValue(Canvas.ZIndexProperty, 1);

                Dock.Children.Add(b);
            }
        }

        private void InitializeDock()
        {
            this.DockBackgroundStrokeThickness = 1;

            Color transparent = new Color();
            transparent.A = 0;
            transparent.R = transparent.G = transparent.B = 255;

            this.DefaultWidthHeight = 48;
            this.ScaleMultipliers = new double[] { 3.0, 2.0, 1.67, 1.34 };

            Dock.MouseLeave += new MouseEventHandler(OnMouseLeave);
            Dock.Background = new SolidColorBrush(transparent);
            Dock.SizeChanged += new SizeChangedEventHandler(OnSizeChanged);

            this.LoadImages();

            Color gray = new Color();
            gray.A = 255;
            gray.R = gray.G = gray.B = 128;

            this.DockBackgroundColor = gray;
            DockBackground = new Rectangle();
            DockBackground.Fill = new SolidColorBrush(this.DockBackgroundColor);
            DockBackground.Opacity = 0.3;
            DockBackground.SetValue(Canvas.ZIndexProperty, 0);

            Color stroke = new Color();
            stroke.A = 255;
            stroke.R = stroke.G = stroke.B = 232;
            DockBackground.Stroke = new SolidColorBrush(stroke);
            DockBackground.StrokeThickness = this.DockBackgroundStrokeThickness;

            Dock.Children.Add(DockBackground);

            // This will init the Dock grid features related to orientation ... invoke this after the rest of the initialization
            this.Orientation = DockOrientation.Bottom;
        }

        private void ResetOrientation()
        {
            Dock.RowDefinitions.Clear();
            Dock.ColumnDefinitions.Clear();
            
            switch (this.Orientation)
            {
                case DockOrientation.Bottom:
                    {
                        Dock.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
                        Dock.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
                        Dock.RowDefinitions.Add(new RowDefinition());
                        DockBackground.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
                        DockBackground.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
                        DockBackground.SetValue(Grid.RowProperty, 0);

                        // A little something special for bottom orientation ...
                        LinearGradientBrush lgb = new LinearGradientBrush();
                        lgb.StartPoint = new Point(0.5, 0);
                        lgb.EndPoint = new Point(0.5, 1);
                        lgb.GradientStops = new GradientStopCollection();
                        GradientStop gs = new GradientStop();
                        gs.Color = Color.FromArgb(0xff, 0, 0, 0);
                        lgb.GradientStops.Add(gs);
                        gs = new GradientStop();
                        gs.Color = Color.FromArgb(0xff, 0x4d, 0x4d, 0x4d);
                        gs.Offset = 1;
                        lgb.GradientStops.Add(gs);
                        gs = new GradientStop();
                        gs.Color = Color.FromArgb(0xff, 0x23, 0x23, 0x23);
                        lgb.GradientStops.Add(gs);
                        DockBackground.Fill = lgb;

                        PlaneProjection pp = new PlaneProjection();
                        pp.RotationX = -65;
                        DockBackground.Projection = pp;

                        DockBackground.Opacity = 1.0;
                        // end of something special

                        break;
                    }
                case DockOrientation.Right:
                case DockOrientation.Left:
                    {
                        Dock.HorizontalAlignment = this.Orientation == DockOrientation.Right ? System.Windows.HorizontalAlignment.Right : System.Windows.HorizontalAlignment.Left;
                        Dock.VerticalAlignment = System.Windows.VerticalAlignment.Center;
                        Dock.ColumnDefinitions.Add(new ColumnDefinition());
                        DockBackground.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
                        DockBackground.HorizontalAlignment = this.Orientation == DockOrientation.Right ? System.Windows.HorizontalAlignment.Right : System.Windows.HorizontalAlignment.Left;
                        DockBackground.SetValue(Grid.ColumnProperty, 0);
                        DockBackground.Projection = null;
                        break;
                    }
                default:
                    break;
            }
            int constantDimensionIndex = 0;
            int variableDimensionIndex = 0;
            foreach (UIElement uie in Dock.Children)
            {
                if (uie.GetType() != typeof(Button))
                    continue;

                Button b = (Button) uie;
                b.Width = b.Height = this.DefaultWidthHeight;

                switch (this.Orientation)
                {
                    case DockOrientation.Bottom:
                        {
                            b.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
                            b.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
                            b.SetValue(Grid.RowProperty, constantDimensionIndex);
                            b.SetValue(Grid.ColumnProperty, variableDimensionIndex);
                            ColumnDefinition cd = new ColumnDefinition();
                            cd.SetValue(ColumnDefinition.WidthProperty, new GridLength(this.DefaultWidthHeight));
                            Dock.ColumnDefinitions.Add(cd);
                            break;
                        }
                    case DockOrientation.Left:
                    case DockOrientation.Right:
                        {
                            b.HorizontalAlignment = this.Orientation == DockOrientation.Right ? System.Windows.HorizontalAlignment.Right : System.Windows.HorizontalAlignment.Left;
                            b.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
                            b.SetValue(Grid.RowProperty, variableDimensionIndex);
                            b.SetValue(Grid.ColumnProperty, constantDimensionIndex);
                            RowDefinition rd = new RowDefinition();
                            rd.SetValue(RowDefinition.HeightProperty, new GridLength(this.DefaultWidthHeight));
                            Dock.RowDefinitions.Add(rd);
                            break;
                        }
                    default:
                        break;
                }
                variableDimensionIndex++;
            }
            if (this.Orientation == DockOrientation.Bottom)
            {
                DockBackground.SetValue(Grid.RowSpanProperty, 1);
                DockBackground.SetValue(Grid.ColumnSpanProperty, variableDimensionIndex);
                DockBackground.Height = this.DefaultWidthHeight + 10;
                DockBackground.Width = double.NaN; // Ensures stretch works
                DockBackground.Margin = new Thickness(0, 0, 0, -0.5 * this.DefaultWidthHeight);
            }
            else
            {
                DockBackground.SetValue(Grid.RowSpanProperty, variableDimensionIndex);
                DockBackground.SetValue(Grid.ColumnSpanProperty, 1);
                DockBackground.Width = this.DefaultWidthHeight + 10;
                DockBackground.Height = double.NaN; // Ensures stretch works
            }
            this.RenderInactiveDock(null, null);
        }
        #endregion

        #region Dock Rendering
        private void RenderInactiveDock(object sender, RoutedEventArgs e)
        {
            this.RenderActiveDock(null, e);
            return;
        }

        private void RenderActiveDock(object sender, RoutedEventArgs e)
        {
            int selected = int.MinValue;
            DependencyProperty property = this.Orientation == DockOrientation.Bottom ? Grid.ColumnProperty : Grid.RowProperty;
            if (sender != null)
                selected = (int)((Button)sender).GetValue(property);

            foreach (UIElement uie in Dock.Children)
            {
                if (uie.GetType() != typeof(Button))
                    continue;
                Button b = (Button)uie;

                if (sender != null)
                {
                    int scaleMultiplierIndex = Math.Abs(selected - (int)((Button)b).GetValue(property));
                    b.Width = b.Height = this.DefaultWidthHeight * (scaleMultiplierIndex < this.ScaleMultipliers.Length ? this.ScaleMultipliers[scaleMultiplierIndex] : 1.0);
                }
                else
                {
                    b.Width = b.Height = this.DefaultWidthHeight;
                }

                if (this.Orientation == DockOrientation.Bottom)
                {
                    ColumnDefinition cd = Dock.ColumnDefinitions[(int)((Button)b).GetValue(property)];
                    cd.SetValue(ColumnDefinition.WidthProperty, new GridLength(b.Width));
                }
                else
                {
                    RowDefinition rd = Dock.RowDefinitions[(int)((Button)b).GetValue(property)];
                    rd.SetValue(RowDefinition.HeightProperty, new GridLength(b.Height));
                }
            }
        }
        #endregion

        #region Event Handlers
        
        private void OnMouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            this.RenderActiveDock(sender, e);
            return;
        }

        private void OnMouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            this.RenderInactiveDock(sender, e);
            return;
        }
        
        private void OnClick(object sender, System.Windows.RoutedEventArgs e)
        {
            MessageBox.Show("Hello");
        }

        private void OnSizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
        {
            // This changes when the UIElement becomes visible, and actual size data is available at this time.
            // So late bind to this data to reset the clipping rectangle.
            RectangleGeometry r = new RectangleGeometry();
            r.Rect = new Rect(0, 0, LayoutRoot.ActualWidth, LayoutRoot.ActualHeight);
            LayoutRoot.Clip = r;
        }
        #endregion

        #region Drivers
        private void OnChangeOrientation(object sender, System.Windows.RoutedEventArgs e)
        {
            if (((string)(((RadioButton)sender).Content)).CompareTo("Left") == 0)
                this.Orientation = DockOrientation.Left;
            else if (((string)(((RadioButton)sender).Content)).CompareTo("Right") == 0)
                this.Orientation = DockOrientation.Right;
            else if (((string)(((RadioButton)sender).Content)).CompareTo("Bottom") == 0)
                this.Orientation = DockOrientation.Bottom;
        }

        public MainPage()
        {
            // Required to initialize variables
            InitializeComponent();
            this.InitializeDock();
        }
        #endregion

    }
}